import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Random;

public class GameLoopTest extends JFrame implements ActionListener
{
   private GamePanel gamePanel = new GamePanel();
   private JButton startButton = new JButton("Start");
   private JButton quitButton = new JButton("Quit");
   private JButton pauseButton = new JButton("Pause");
   private boolean running = false;
   private boolean paused = false;
   private int fps = 60;
   private int frameCount = 0;
   
   RigidBody test=new RigidBody();
	RigidBody test2=new RigidBody();
	RigidBody floorBody =new RigidBody();
	ArrayList<RigidBody> rBodies=new ArrayList<RigidBody>();
   
   ydForce gravity;
   
   BodyPart l1=new BodyPart(new ydPoint(100, 100), new ydPoint(100, 130));
   
   Character_Skeleton c1=new Character_Skeleton(new ydPoint(300, 300));
   
   Particle particle=new Particle(new ydPoint(), new ydPoint(3, 3), 100, 10);
   ParticleEmitter PM=new ParticleEmitter(100);
   
   public GameLoopTest()
   {
      super("Fixed Timestep Game Loop Test");
      Container cp = getContentPane();
      cp.setLayout(new BorderLayout());
      JPanel p = new JPanel();
      p.setLayout(new GridLayout(1,2));
      p.add(startButton);
      p.add(pauseButton);
      p.add(quitButton);
      cp.add(gamePanel, BorderLayout.CENTER);
      cp.add(p, BorderLayout.SOUTH);
      setSize(500, 500);
      
      startButton.addActionListener(this);
      quitButton.addActionListener(this);
      pauseButton.addActionListener(this);
      
      
      test.position=new ydPoint(150, 400);
		test.size.x=200;
		test.size.y=200;
		
		test.theta=Globals.degreesToRads(0);
		test.MASS=200;
		
		test.calcCornerPoints();
		test.calculateCenterMass();
		
		test2.position=new ydPoint(175, 50);
		test2.size.x=100;
		test2.size.y=100;
		
		test2.theta=Globals.degreesToRads(0);
		test2.MASS=200;
		
		test2.calcCornerPoints();
		test2.calculateCenterMass();
		
		gravity=new ydForce(new ydPoint(), new ydPoint(0,-10), true);
		
		Random rand =new Random(500);
		for (int i=0; i<5; i++){
			RigidBody t=new RigidBody();
			if(i==0){
				t.velocity=new ydPoint(0, 0.01f);
			
			t.position=new ydPoint(50, 0);
			t.size.x=200;
			t.size.y=20;
			t.theta=0;
			}
			else{
				t.position=new ydPoint(rand.nextInt(400), rand.nextInt(700)+100);
				t.size.x=rand.nextInt(50)+5;
				t.size.y=rand.nextInt(50)+5;
				t.theta=Globals.degreesToRads(rand.nextInt(360));
			}
			
			
			t.MASS=rand.nextInt(200)+50;
			
			t.calcCornerPoints();
			t.calculateCenterMass();
			
			t.addForce(gravity);
			
			rBodies.add(t);
		}
		
		floorBody.position=new ydPoint(0, 750);
		floorBody.size.x=500;
		floorBody.size.y=20;
		
		floorBody.theta=Globals.degreesToRads(0);
		floorBody.MASS=20;
		
		floorBody.calcCornerPoints();
		floorBody.calculateCenterMass();
		
		gravity=new ydForce(new ydPoint(), new ydPoint(0,-10), true);
		
		test.addForce(gravity);
		test2.addForce(gravity);
		
		
   }
   
   public static void main(String[] args)
   {
      GameLoopTest glt = new GameLoopTest();
      glt.setVisible(true);
   }
   
   public void actionPerformed(ActionEvent e)
   {
      Object s = e.getSource();
      if (s == startButton)
      {
         running = !running;
         if (running)
         {
            startButton.setText("Stop");
            runGameLoop();
         }
         else
         {
            startButton.setText("Start");
         }
      }
      else if (s == pauseButton)
      {
        paused = !paused;
         if (paused)
         {
            pauseButton.setText("Unpause");
         }
         else
         {
            pauseButton.setText("Pause");
         }
      }
      else if (s == quitButton)
      {
         System.exit(0);
      }
   }
   
   //Starts a new thread and runs the game loop in it.
   public void runGameLoop()
   {
      Thread loop = new Thread()
      {
         public void run()
         {
            gameLoop();
         }
      };
      loop.start();
   }
   
   //Only run this in another Thread!
   private void gameLoop()
   {
      //This value would probably be stored elsewhere.
      final double GAME_HERTZ = 30.0;
      //Calculate how many ns each frame should take for our target game hertz.
      final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
      //At the very most we will update the game this many times before a new render.
      //If you're worried about visual hitches more than perfect timing, set this to 1.
      final int MAX_UPDATES_BEFORE_RENDER = 5;
      //We will need the last update time.
      double lastUpdateTime = System.nanoTime();
      //Store the last time we rendered.
      double lastRenderTime = System.nanoTime();
      
      //If we are able to get as high as this FPS, don't render again.
      final double TARGET_FPS = 60;
      final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;
      
      //Simple way of finding FPS.
      int lastSecondTime = (int) (lastUpdateTime / 1000000000);
      
      while (running)
      {
         double now = System.nanoTime();
         int updateCount = 0;
         
         if (!paused)
         {
             //Do as many game updates as we need to, potentially playing catchup.
            while( now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER )
            {
               updateGame();
               lastUpdateTime += TIME_BETWEEN_UPDATES;
               updateCount++;
            }
   
            //If for some reason an update takes forever, we don't want to do an insane number of catchups.
            //If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
            if ( now - lastUpdateTime > TIME_BETWEEN_UPDATES)
            {
               lastUpdateTime = now - TIME_BETWEEN_UPDATES;
            }
         
            //Render. To do so, we need to calculate interpolation for a smooth render.
            float interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime) / TIME_BETWEEN_UPDATES) );
            drawGame(interpolation);
            lastRenderTime = now;
         
            //Update the frames we got.
            int thisSecond = (int) (lastUpdateTime / 1000000000);
            if (thisSecond > lastSecondTime)
            {
               System.out.println("NEW SECOND " + thisSecond + " " + frameCount);
               fps = frameCount;
               frameCount = 0;
               lastSecondTime = thisSecond;
            }
         
            //Yield until it has been at least the target time between renders. This saves the CPU from hogging.
            while ( now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES)
            {
               Thread.yield();
            
               //This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
               //You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
               //FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this.
               try {Thread.sleep(1);} catch(Exception e) {} 
            
               now = System.nanoTime();
            }
         }
      }
   }
   
   private void updateGame()
   {
      gamePanel.update();
   }
   
   private void drawGame(float interpolation)
   {
      gamePanel.setInterpolation(interpolation);
      gamePanel.repaint();
   }
   
   private class GamePanel extends JPanel
   {
      float interpolation;
      float ballX, ballY, lastBallX, lastBallY;
      int ballWidth, ballHeight;
      float ballXVel, ballYVel;
      float ballSpeed;
      
      int lastDrawX, lastDrawY;
      
      public GamePanel()
      {
         ballX = lastBallX = 100;
         ballY = lastBallY = 100;
         ballWidth = 25;
         ballHeight = 25;
         ballSpeed = 25;
         ballXVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
         ballYVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
      }
      
      public void setInterpolation(float interp)
      {
         interpolation = interp;
      }
      
      public void update()
      {
         lastBallX = ballX;
         lastBallY = ballY;
         
         ballX += ballXVel;
         ballY += ballYVel;
         
         if (ballX + ballWidth/2 >= getWidth())
         {
            ballXVel *= -1;
            ballX = getWidth() - ballWidth/2;
            ballYVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
         }
         else if (ballX - ballWidth/2 <= 0)
         {
            ballXVel *= -1;
            ballX = ballWidth/2;
         }
         
         if (ballY + ballHeight/2 >= getHeight())
         {
            ballYVel *= -1;
            ballY = getHeight() - ballHeight/2;
            ballXVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
         }
         else if (ballY - ballHeight/2 <= 0)
         {
            ballYVel *= -1;
            ballY = ballHeight/2;
         }
        /* 
         if(RigidBody.collisionOccured(test, test2)){
			//paint.setColor(Color.RED);
		}
		else{
			test2.addForce(gravity);
			//paint.setColor(Color.GREEN);
		}*/

/*		test2.calculateNextPosition(new ydPoint());
		test.calculateNextPosition(new ydPoint());
		*/
		
		
		/*for(RigidBody r: rBodies){
			for(RigidBody r2: rBodies){
				RigidBody.collisionOccured(r, r2);
				r.calculateNextPosition(new ydPoint());
				r2.calculateNextPosition(new ydPoint());
			}
		}*/
         
      }
 float i=0.05f;
      public void paintComponent(Graphics g)
      {
         //BS way of clearing out the old rectangle to save CPU.
         
         g.setColor(getBackground());
         //g.fillRect(lastDrawX-1, lastDrawY-1, ballWidth+2, ballHeight+2);
        // g.fillRect(5, 0, 75, 30);
         g.fillRect(0, 0, 2000, 2000);
         
         g.setColor(Color.RED);
         int drawX = (int) ((ballX - lastBallX) * interpolation + lastBallX - ballWidth/2);
         int drawY = (int) ((ballY - lastBallY) * interpolation + lastBallY - ballHeight/2);
         //g.fillOval(drawX, drawY, ballWidth, ballHeight);
         
         lastDrawX = drawX;
         lastDrawY = drawY;
         
         g.setColor(Color.BLACK);
         g.drawString("FPS: " + fps, 5, 10);
         
       /* test.drawBody(g);
		test2.drawBody(g);*/
         PM.emitParticle();
         PM.update();
         PM.draw(g);
         /*particle.update();
         particle.draw(g);*/
         
		c1.draw(g);
		c1.run();
/*		c1.RightArm.limb.rotate(c1.RightArm.limb.p1, i);
		//l1.draw(g);
		if(c1.RightArm.limb.angle<30){
		i=0.05f;
		}
		else if(c1.RightArm.limb.angle>160){
			i=-0.05f;
		}*/
		//l1.rotate(l1.p1, ydMath.degreesToRads(i));
		/*for(RigidBody r: rBodies){
			r.drawBody(g);
		}*/
		
/*		floorBody.drawBody(g);
		
		if(RigidBody.POCTest(floorBody, test, g)){
			//test.addForce(gravity);
		}
		else{
			test.stopAcceleration();
		}*/
		/*RigidBody.POCTest(floorBody, test2,g );
		RigidBody.POCTest(test, test2,g );
         
		for(RigidBody r: rBodies){
			for(RigidBody r2: rBodies){
				RigidBody.POCTest(r, r2,g );
				
				r2.calculateNextPosition(new ydPoint());
			}
			RigidBody.POCTest(floorBody, r,g );
			r.calculateNextPosition(new ydPoint());
			
		}*/
		
         frameCount++;
      }
   }
   
   private class Ball
   {
      float x, y, lastX, lastY;
      int width, height;
      float xVelocity, yVelocity;
      float speed;
      
      public Ball()
      {
         width = (int) (Math.random() * 50 + 10);
         height = (int) (Math.random() * 50 + 10);
         x = (float) (Math.random() * (gamePanel.getWidth() - width) + width/2);
         y = (float) (Math.random() * (gamePanel.getHeight() - height) + height/2);
         lastX = x;
         lastY = y;
         xVelocity = (float) Math.random() * speed*2 - speed;
         yVelocity = (float) Math.random() * speed*2 - speed;
      }
      
      public void update()
      {
         lastX = x;
         lastY = y;
         
         x += xVelocity;
         y += yVelocity;
         
         if (x + width/2 >= gamePanel.getWidth())
         {
            xVelocity *= -1;
            x = gamePanel.getWidth() - width/2;
            yVelocity = (float) Math.random() * speed*2 - speed;
         }
         else if (x - width/2 <= 0)
         {
            xVelocity *= -1;
            x = width/2;
         }
         
         if (y + height/2 >= gamePanel.getHeight())
         {
            yVelocity *= -1;
            y = gamePanel.getHeight() - height/2;
            xVelocity = (float) Math.random() * speed*2 - speed;
         }
         else if (y - height/2 <= 0)
         {
            yVelocity *= -1;
            y = height/2;
         }
      }
      
      public void draw(Graphics g)
      {
         
      }
   }
}